// 字符数
package big

import (
	"strconv"
	"strings"
)

var 小数精度 int64 = 100

/*
	数相比大

原理：

	先去除首位多余且影响判断的0，后
	位数多的为大
	位数相同，不一样，逐从首位一比较每一位，先更大的为大
	位数相同，一样，相等
*/
func 数相比大(数1, 数1名称, 数2, 数2名称 string) string {
	var (
		数1位数        = strings.Count(数1, "") - 1
		数2位数        = strings.Count(数2, "") - 1
		存在零  bool   = false
		零个数  uint64 = 0
	)
	存在零, 零个数 = 寻找首位零(数1)
	if 存在零 == true { //首位存在0，去除
		if int(零个数) == len(数1) {
			数1 = "0"
		} else {
			数1 = 数1[零个数:]
		}
	}
	存在零, 零个数 = 寻找首位零(数2)
	if 存在零 == true { //首位存在0，去除
		if int(零个数) == len(数2) {
			数2 = "0"
		} else {
			数2 = 数2[零个数:]
		}
	}
	数1位数 = strings.Count(数1, "") - 1
	数2位数 = strings.Count(数2, "") - 1
	if 数1位数 > 数2位数 {
		值, _ := strconv.ParseInt(数1, 10, 64)
		if 值 != 0 {
			return 数1名称
		}
	} else if 数2位数 > 数1位数 {
		值, _ := strconv.ParseInt(数2, 10, 64)
		if 值 != 0 {
			return 数2名称
		}
	} else {
		for 索引 := 0; 索引 < 数1位数; 索引++ {
			数1值, _ := strconv.ParseUint(string(数1[索引]), 10, 64)
			数2值, _ := strconv.ParseUint(string(数2[索引]), 10, 64)
			if 数1值 > 数2值 {
				return 数1名称
			} else if 数2值 > 数1值 {
				return 数2名称
			}
			// else if 数1值 == 数2值 {
			// }
		}
		return "相等"
	}
	return ""
}

/*
	数相加

原理：

	模拟人类笔算，从末位开始同位相加加进位，插入和前，非同位，加进位，插入和前
*/
func 数相加(加数1, 加数2 string, 加数1位数, 加数2位数 uint64) string { //将两个数相加
	var (
		加数1循环次数 uint64 = 加数1位数
		加数2循环次数 uint64 = 加数2位数
		字符和     string     //和
		进位      int64  = 0 //进位
		和缺损     string     //需要补充的和（部分）
	)
	for 加数1循环次数 > 0 && 加数2循环次数 > 0 { //两个加数都有用于计算的同一位
		加数1索引 := 加数1循环次数 - 1                                    //加数1未计算末位
		加数2索引 := 加数2循环次数 - 1                                    //加数2未计算末位
		临时和1, _ := strconv.ParseInt(string(加数1[加数1索引]), 10, 64) //加数1计算位int64格式
		临时和2, _ := strconv.ParseInt(string(加数2[加数2索引]), 10, 64) //加数2计算位int64格式
		临时和 := 临时和1 + 临时和2                                      //两加数相加
		临时和 += 进位
		进位 = 0                                     //与前一位进位相加
		if 临时和 >= 10 && 加数1索引 == 0 && 加数2索引 == 0 { //首位计算，大于等于10，直接加入和
			字符和 = strconv.FormatInt(临时和, 10) + 字符和
			进位 = 1
		} else if 临时和 >= 10 { //非首位，大于等于10，去十进位，个位入和
			字符和 = string(strconv.FormatInt(int64(临时和-10), 10)) + 字符和
			进位 = 1
		} else { //小于10，直接加入和
			字符和 = strconv.FormatInt(临时和, 10) + 字符和
			进位 = 0
		}
		加数1循环次数 -= 1 //使加数1索引减1
		加数2循环次数 -= 1 //使加数2索引减1
	}
	if 加数1循环次数 > 0 { //计算完后加数1还有未计算位
		if 进位 == 1 { //有进位，剩余加1入和
			和缺损 = 和修正(加数1[0:加数1循环次数])
		} else { //否则，直接入和
			和缺损 = 加数1[0:加数1循环次数]
		}
		字符和 = 和缺损 + 字符和
	} else if 加数2循环次数 > 0 { //计算完后加数2还有未计算位
		if 进位 == 1 { //有进位，剩余加1入和
			和缺损 = 和修正(加数2[0:加数2循环次数])
		} else { //否则，直接入和
			和缺损 = 加数2[0:加数2循环次数]
		}
		字符和 = 和缺损 + 字符和
	}
	return 字符和 //返回和
}

/*
	数相减

原理：

	模拟人类笔算，从末位开始同位相减，插入差前减退位，非同位，减退位，插入差前
	被减数大于减数，减数减（以上述方法减）被减数取反
*/
func 数相减(被减数, 减数 string, 被减数位数, 减数位数 uint64) (string, bool) { //将两个数相减
	var (
		被减数循环次数 uint64 = 被减数位数
		减数循环次数  uint64 = 减数位数
		差       string     //计算的差
		临时差     int64      //每次一位计算后临时保存的差
		退位      uint64 = 0 //退位
		差缺损     string     //需要补充的差（部分）
		// 存在零     bool   = false //首位是否存在0
		// 零个数     uint64         //首位0的个数
		结果为正 bool = true //结果是否为正
	)
	大数名 := 数相比大(被减数, "被减数", 减数, "减数")
	if 大数名 == "减数" {
		结果为正 = false
		被减数, 减数 = 减数, 被减数
	}
	for 被减数循环次数 > 0 && 减数循环次数 > 0 { //两个减数都有用于计算的同一位
		被减数索引 := 被减数循环次数 - 1                                    //被减数未计算末位
		减数索引 := 减数循环次数 - 1                                      //减数未计算末位
		临时差1, _ := strconv.ParseInt(string(被减数[被减数索引]), 10, 64) //被减数计算位uint64格式
		临时差2, _ := strconv.ParseInt(string(减数[减数索引]), 10, 64)   //减数计算位uint64格式
		临时差 = 临时差1 - 临时差2                                       //两减数相减
		临时差 = 临时差 - int64(退位)                                   //减退位
		退位 = 0
		if 临时差 < 0 { //应当退位
			临时差 += 10
			退位 = 1
		}
		差 = strconv.FormatInt(临时差, 10) + 差 //保存差
		被减数循环次数 -= 1                       //使被减数索引减1
		减数循环次数 -= 1                        //使减数1索引减1
	}
	if 被减数循环次数 > 0 { //计算完后被减数还有未计算位
		if 退位 == 1 { //有进位，剩余减1入差
			差缺损 = 差修正(被减数[0:被减数循环次数])
		} else { //否则，直接入差
			差缺损 = 被减数[0:被减数循环次数]
		}
		差 = 差缺损 + 差
	} else if 减数循环次数 > 0 { //计算完后减数还有未计算位
		if 退位 == 1 { //有进位，剩余减1入差
			差缺损 = 差修正(减数[0:减数循环次数])
		} else { //否则，直接入差
			差缺损 = 减数[0:减数循环次数]
		}
		差 = 差缺损 + 差
	}
	// 存在零, 零个数 = 寻找首位零(差)
	// if 存在零 == true { //首位存在0，去除
	// 	if int(零个数) == len(差) {
	// 		return "0", 结果为正
	// 	} else {
	// 		差 = 差[零个数:]
	// 	}
	// }
	return 差, 结果为正 //返回差
}

/*
	数相乘

原理：

	两数相乘的全实现函数
	模拟人类笔算，本函数实现笔算后算得值相加，算得值调用各位相乘
*/
func 数相乘(乘数1, 乘数2 string, 乘数1位数, 乘数2位数 uint64) string { //将两个数相乘
	var (
		字符积   string   //积
		被记录积  string   //被记录的积的一部分（积-剩余积）
		积的部分值 []string //所有（积-积的剩下值）
	)
	if 乘数2位数 >= 乘数1位数 { //使乘数1为较多位或相同位的
		乘数1, 乘数2 = 乘数2, 乘数1
		乘数1位数, 乘数2位数 = 乘数2位数, 乘数1位数
	}
	积的部分值 = make([]string, 乘数1位数)
	for 循环次数 := int64(乘数2位数 - 1); 循环次数 > -1; 循环次数-- { //调用各位相乘获得积的部分值
		添0个数 := (乘数2位数 - 1) - uint64(循环次数)
		被记录积 = 各位相乘(乘数1, string(乘数2[循环次数]), 乘数1位数, 添0个数)
		if 循环次数 == int64(乘数2位数-1) {
			积的部分值 = append(积的部分值[:0], 被记录积)
		} else {
			积的部分值 = append(积的部分值, 被记录积)
		}
	}
	for 循环次数 := 0; 循环次数 < len(积的部分值); 循环次数++ { //积的部分值相加的积
		字符积 = 数相加(积的部分值[循环次数], 字符积, uint64(len(积的部分值[循环次数])), uint64(len(字符积)))
	}
	return 字符积 //返回和
}

/*
	各位相乘

原理：

	模拟人类笔算，乘数2分别乘乘数1每一位，加上进位，结果大于10，留9，剩余值进位，最终结果入积
*/
func 各位相乘(乘数1, 乘数2 string, 乘数1位数, 添0个数 uint64) string { //使乘数1（多位数）的每一位与乘数2（个数）相乘
	var (
		积   string
		进位  uint64 = 0
		临时积 uint64
	)
	临时乘数2, _ := strconv.ParseUint(乘数2, 10, 64)        //乘数2的uint64形式
	for 循环次数 := int64(乘数1位数 - 1); 循环次数 >= 0; 循环次数-- { //使乘数1每一位与乘数2相乘
		临时乘数1, _ := strconv.ParseUint(string(乘数1[循环次数]), 10, 64) //乘数1的uint64形式
		临时积 = 临时乘数1 * 临时乘数2                                      //乘数1某一位与乘数2相乘
		临时积 = 临时积 + 进位                                           //加进位
		进位 = 0                                                   //进位清0
		if 临时积 >= 10 && 循环次数 != 0 {                              //积不是一位数且应当进位
			进位 = 临时积 / 10
			临时积 = 临时积 % 10
		}
		积 = strconv.FormatUint(临时积, 10) + 积
	}
	积 = 积 + strings.Repeat("0", int(添0个数)) //积添笔算应补充的0
	return 积
}

/*
	确定实际计算被除数

原理：

	先在适当时（计算已开始大于1次后）将余数添加到被除数首位
	如被除数大于除数，自首位逐位取大于除数的被除数部分，去除被取部分，返回被取部分
	如被除数小于除数，重复被除数末尾添0，直至添0后值大于除数，返回该值
*/
func 确定实际计算被除数(被除数 *string, 除数 string, 余数 string, 已计算位数 int64) (string, uint64) {
	var (
		大数    string     //记录被除数与除数的大小关系（哪个大或相等）
		增加小数位 uint64 = 0 //记录确定实际计算被除数过程中增加了几个小数位数
	)
	if 已计算位数 != 0 { //如果应该添加余数到被除数前
		if 余数 != "0" { //如果余数不等于0
			(*被除数) = 余数 + (*被除数) //添加余数到被除数前
		}
	}
	for 循环次数 := 0; 循环次数 <= len((*被除数)); 循环次数++ { //自首位逐位取被除数部分
		大数 = 数相比大((*被除数)[0:循环次数], "被除数", 除数, "除数") //获取自首位逐位取被除数部分与除数的大小关系（哪个大或相等）
		if 大数 == "被除数" {                           //如被除数部分大
			返回值 := (*被除数)[0:循环次数]  //返回值等于被除数被取部分
			(*被除数) = (*被除数)[循环次数:] //被除数去除被取部分
			return 返回值, 增加小数位      //返回返回值与增加小数位
		} else if 大数 == "相等" {
			返回值 := (*被除数)[0:循环次数]
			if 循环次数 == len((*被除数)) {
				(*被除数) = "0"
			} else {
				(*被除数) = (*被除数)[循环次数:]
			}
			return 返回值, 增加小数位 //返回返回值与增加小数位
		}
	}
	for { //添0直至添0后值大于除数
		(*被除数) = (*被除数) + "0"
		增加小数位 += 1
		大数 = 数相比大((*被除数), "被除数", 除数, "除数")
		if 大数 == "相等" {
			return (*被除数), 增加小数位 //返回返回值与增加小数位
		} else if 大数 == "被除数" {
			值, _ := strconv.ParseInt((*被除数), 10, 64)
			if 值 != 0 {
				return (*被除数), 增加小数位 //返回返回值与增加小数位
			}
		}
	}
	return "", 增加小数位 //为了编译通过的无用代码
}

/*
	数相除

原理：

	模拟人类笔算（自首位逐位取大于除数的被除数部分，取相当于几个除数的整倍数，剩余为余数代入剩余被除数重复上述过程），本函数实现商的各个值依序拼接
	调用确定实际计算被除数函数和各位相除函数获得依序的商的各个值
	确定实际计算被除数函数辅助作用：可以时将被除数已计算部分去除
*/
func 数相除(被除数, 除数 string, 小数精度 int64) (string, uint64) { //将两个数相除
	var (
		字符商       string                                         //商，为商的各个值依序拼接
		被记录商      string                                         //被记录的商的一部分（商-剩余商）
		余数        string                                         //记录余数
		被除数位数     int64  = int64(strings.Count(被除数, "") - 1)     //记录被除数位数
		实际计算被除数   string                                         //记录实际计算被除数
		实际计算被除数位数 int64  = int64(strings.Count(实际计算被除数, "") - 1) //记录实际计算被除数位数
		已计算位数     int64  = 0                                     //记录已经计算的数有多少位
		除数位数             = uint64(strings.Count(除数, "") - 1)     //记录除数位数
		运算结束      bool   = false                                 //记录余数是否结束
		总位数       uint64 = 0                                     //记录商的总位数
		小数位数      uint64 = 0                                     //记录商的小数位数
		增加小数位数    uint64                                         //记录商的小数位数增加了多少
		无需补0      bool   = false                                 //商需要补0而是否补0
	)
	for 计算结束 := false; 计算结束 == false; { //如果还可以计算
		实际计算被除数, 增加小数位数 = 确定实际计算被除数(&被除数, 除数, 余数, 已计算位数)  //获得商的某一个值的倍数，可以时将被除数已计算部分去除
		实际计算被除数位数 = int64(strings.Count(实际计算被除数, "") - 1) //设置实际计算被除数有多少位
		被记录商, 余数 = 各位相除(实际计算被除数, 除数, 实际计算被除数位数)           //获得商的某一个值
		if 增加小数位数 != 0 {                                  //若被除数已计算部分全应去除,同时可能商的某一个值不添适量的0会使拼接后的商不正确
			被除数 = ""
			if 无需补0 == false { //商需要补0
				被记录商 = strings.Repeat("0", int(uint64(增加小数位数))) + 被记录商
			}
		}
		无需补0 = false                    //商需要时未补0
		已计算位数 = 已计算位数 + 实际计算被除数位数       //追加上已经计算的位数数量
		if 余数 == "0" && 已计算位数 < 被除数位数 { //如果商的某一个值不添适量的0会使拼接后的商不正确
			无需补0 = true             //商需要时已补0
			var _, 零个数 = 寻找首位零(被除数) //确认未计算被除数首位连续有几个0
			if 零个数 != 0 {           //首位存在0
				被记录商 = 被记录商 + strings.Repeat("0", int(uint64(零个数+除数位数-1))) //添0个数等于（零个数+除数位数-1）
			} else {
				被记录商 = 被记录商 + strings.Repeat("0", int(uint64(除数位数-1))) //添0个数等于（除数位数-1）
			}
		}
		字符商 = 字符商 + 被记录商 //将已经可以拼接的商的某一个值进行拼接
		小数位数 += 增加小数位数   //增加商在计算中增加的小数位数
		if 余数 == "0" {   //计算可能结束（如果余数为0）
			值, _ := strconv.ParseInt(被除数, 10, 64) //记录未计算被除数的int64形式
			if 值 == 0 {                           //计算结束（未计算被除数等于0）
				运算结束 = true
			}
		}
		if (小数位数 >= uint64(小数精度) || 运算结束 == true) || (小数位数 >= uint64(小数精度) && 运算结束 == true) { //如果已经计算出商或达到近似小数位数要求
			计算结束 = true //设置计算结束
		}
	}
	总位数 = uint64(len(字符商))   //记录商的总位数
	return 字符商, (总位数 - 小数位数) //返回和与整数位数
}

/*
	各位相除

原理：

	连续用实际计算被除数减除数，减一次商加1，直至实际计算小于除数减除数，返回商与剩余实际计算被除数
*/
func 各位相除(实际计算被除数, 除数 string, 实际计算被除数位数 int64) (string, string) { //取商返回，即实际计算被除数是除数的几倍，并返回以及剩余不到1倍的部分
	var (
		商   string         //实际计算被除数除以除数的整数商
		临时商 int64          //临时保存商的uint64形式
		为正  bool   = false //记录是否实际计算被除数>除数
	)
	for 可计算 := true; 可计算 == true; { //如果实际计算被除数>除数
		实际计算被除数, 为正 = 数相减(实际计算被除数, 除数, uint64(实际计算被除数位数), uint64(strings.Count(除数, "")-1)) //实际计算被除数减除数
		实际计算被除数位数 = int64(strings.Count(实际计算被除数, "") - 1)                                  //重新确定实际计算被除数位数
		if 为正 == false {
			可计算 = false
			商 = strconv.FormatInt(临时商, 10)
		} else if 实际计算被除数 == "0" {
			可计算 = false
			临时商 += 1
			商 = strconv.FormatInt(临时商, 10)
		} else if 大数名 := 数相比大(实际计算被除数, "实际计算被除数", 除数, "除数"); 大数名 == "除数" {
			临时商 += 1
			可计算 = false
			商 = strconv.FormatInt(临时商, 10)
		} else { //如果实际计算被除数>除数
			临时商 += 1
		}
	}
	return 商, 实际计算被除数 //返回实际计算被除数是除数的几倍与剩余实际计算被除数
}

//数四则运算与比较完------------------------------------------------------------------------------------------------

/*
	和修正

原理：

	其中一加数为1,末位必加1,其他位有进位或应当进位做相应处理，结果插入修正和前
*/
func 和修正(和部分 string) string { //修正和部分（使修正和加1）
	var (
		修正结果 string                                      //记录和部分修正结果
		修正位数 uint64 = uint64(strings.Count(和部分, "") - 1) //记录和部分位数
		循环次数        = 修正位数                               //循环次数 = 修正位数
		修正部分 int64                                       //临时保存某位修正部分
		进位   bool                                        //临时保存是否存在进位
	)
	for 循环次数 > 0 {
		索引 := 循环次数 - 1                                      //索引=和部分位数数组索引值
		修正部分, _ = strconv.ParseInt(string(和部分[索引]), 10, 64) //修正部分=和部分[索引]的int格式
		if 索引 == 修正位数-1 {                                   //是最后一位
			修正部分 += 1
		}
		if 进位 == true { //存在进位
			修正部分 += 1
			进位 = false
		}
		if 修正部分 > 10 { //应该进位
			修正部分 -= 10
			进位 = true
		}
		修正结果 = strconv.FormatInt(修正部分, 10) + 修正结果 //保存修正结果（string格式）
		循环次数 -= 1                                 //循环次数-1
	}
	return 修正结果 //返回修正结果
}

/*
	差修正

原理：

	其中一减数为1,末位必减1,其他位有退位或应当退位做相应处理，结果插入修差和前
*/
func 差修正(差部分 string) string { //修正差部分（使修正差减1）
	var (
		修正结果 string                                      //记录差部分修正结果
		修正位数 uint64 = uint64(strings.Count(差部分, "") - 1) //记录差部分位数
		循环次数        = 修正位数                               //循环次数 = 修正位数
		修正部分 int64                                       //临时保存某位修正部分
		退位   bool                                        //临时保存是否存在退位
	)
	for 循环次数 > 0 {
		索引 := 循环次数 - 1                                      //索引=差部分位数数组索引值
		修正部分, _ = strconv.ParseInt(string(差部分[索引]), 10, 64) //修正部分=差部分[索引]的int格式
		if 索引 == 修正位数-1 {                                   //是最后一位
			修正部分 -= 1
		}
		if 退位 == true { //存在退位
			修正部分 -= 1
			退位 = false
		}
		if 修正部分 < 0 { //应该退位
			修正部分 += 10
			退位 = true
		}
		修正结果 = strconv.FormatInt(修正部分, 10) + 修正结果 //保存修正结果（string格式）
		循环次数 -= 1                                 //循环次数-1
	}
	return 修正结果 //返回修正结果
}

/*
	寻找首位零

原理：

	从首位向末位遍历每一个数字，首位不存在0,退出并返回flase与0，存在0,返回true与首位存在的连续0的个数
*/
func 寻找首位零(寻找部分 string) (bool, uint64) { //寻找数字字符串是否首位是零，如果是，确定首位零的个数
	var (
		首位存在零 bool   = false                       //是否数字字符串是否首位是零，默认不是
		零位数   uint64 = 0                           //从首位算起零的个数，默认是0个
		位数           = strings.Count(寻找部分, "") - 1 //数字字符串的位数
	)
	if 寻找部分 == "0" {
		return 首位存在零, 零位数
	}
	for 循环次数 := 0; 循环次数 < 位数; 循环次数++ { //循环位数次
		if 寻找部分[循环次数] == '0' && 循环次数 == 0 { //首位存在0
			首位存在零 = true
			零位数 += 1
			continue
		} else if 寻找部分[循环次数] == '0' { //首位后存在0
			零位数 += 1
			continue
		} else if 寻找部分[循环次数] != '0' { //首位后不存在0，退出
			break
		}
		if 首位存在零 == false { //如果首位不存在0，退出
			break
		}
	}
	return 首位存在零, 零位数
}
